package com.cxm.helper.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cxm.helper.enums.LikedStatusEnum;
import com.cxm.helper.enums.MessageBusinessTypeEnum;
import com.cxm.helper.enums.MessageTypeEnum;
import com.cxm.helper.pojo.Article;
import com.cxm.helper.pojo.Message;
import com.cxm.helper.pojo.UserLike;
import com.cxm.helper.service.ArticleService;
import com.cxm.helper.service.UserLikeService;
import com.cxm.helper.mapper.UserLikeMapper;
import com.cxm.helper.util.RedisKeyUtils;
import com.cxm.helper.util.RedisUtil;
import com.cxm.helper.util.constant.MessageTemplateConstant;
import com.cxm.helper.util.constant.RedisKeyConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author chenximeng
 * @description 针对表【user_like(用户点赞表)】的数据库操作Service实现
 * @createDate 2022-11-30 14:31:57
 */
@Slf4j
@Service
public class UserLikeServiceImpl extends ServiceImpl<UserLikeMapper, UserLike>
        implements UserLikeService {

    @Autowired
    RedisUtil redisUtil;
    @Autowired
    RedisTemplate<String, String> redisTemplate;
    @Autowired
    ArticleService articleService;
    @Autowired
    RabbitMqService rabbitMqService;

    @Override
    public Map<Long, Integer> articleListIsLiked(List<Long> articleIds, Long userId) {
        Map<Long, Integer> map = new HashMap<>(articleIds.size());
        List<UserLike> redisList = getListByArticleIdsAndUserIdFromRedis(articleIds, userId);
        List<UserLike> dbList = list(new QueryWrapper<UserLike>().eq( "user_id", userId).in("article_id", articleIds));
        if (CollUtil.isEmpty(redisList) && CollUtil.isEmpty(dbList)) {
            for (Long articleId : articleIds) {
                map.put(articleId, LikedStatusEnum.UNLIKE.getCode());
            }
            return map;
        }
        Map<Long, Integer> redisMap = new HashMap<>(redisList.size());
        Map<Long, Integer> dbMap = new HashMap<>(redisList.size());
        if (CollUtil.isNotEmpty(redisList)) {
            redisMap = redisList.stream().collect(Collectors.toMap(UserLike::getArticleId, UserLike::getLikeStatus));
        }
        if (CollUtil.isNotEmpty(dbList)) {
            dbMap = dbList.stream().collect(Collectors.toMap(UserLike::getArticleId, UserLike::getLikeStatus));
        }
        for (Long articleId : articleIds) {
            Integer redisStatus = redisMap.get(articleId);
            Integer dbStatus = dbMap.get(articleId);
            if (redisStatus == null && dbStatus == null) {
                map.put(articleId, LikedStatusEnum.UNLIKE.getCode());
                continue;
            }
            if (redisStatus!=null) {
                map.put(articleId, redisStatus);
                continue;
            }
            map.put(articleId, dbStatus);

        }
        return map;
    }

    @Override
    public Map<Long, Long> getArticleLikedNumFromRedis(List<Long> articleIds, boolean isRemove) {
        Map<Long, Long> result = new HashMap<>();
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyConstant.MAP_KEY_USER_LIKED_COUNT, ScanOptions.NONE);
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> entry = cursor.next();
            String key = (String) entry.getKey();
            Long articleId = Long.parseLong(key);
            if (articleIds.size() > 0 && !articleIds.contains(articleId)) {
                continue;
            }
            Long value = Long.parseLong((String) entry.getValue());
            result.put(articleId, value);
            if (isRemove) {
                redisTemplate.opsForHash().delete(RedisKeyConstant.MAP_KEY_USER_LIKED_COUNT, key);
            }
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveLiked2Redis(Long userId, Long articleId) {
        String key = RedisKeyUtils.getLikedKey(userId, articleId);
        redisUtil.hset(RedisKeyConstant.MAP_KEY_USER_LIKED, key, LikedStatusEnum.LIKE.getCode().toString());
        incrementLikedCount(articleId);
        Long receiveUserId = articleService.getById(articleId).getCreateUser();
        if (receiveUserId.equals(userId)) {
            return;
        }
        Message message = new Message();
        message.setSendUserId(userId);
        message.setBusinessId(articleId);
        message.setBusinessType(MessageBusinessTypeEnum.ARTICLE.getCode());
        message.setMessageType(MessageTypeEnum.Like.getCode());
        message.setReceiveUserId(receiveUserId);
        message.setContent(MessageTemplateConstant.LIKE_ARTICLE_MESSAGE);
        rabbitMqService.saveMsg(JSONUtil.toJsonStr(message));
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlikeFromRedis(Long userId, Long articleId) {
        String key = RedisKeyUtils.getLikedKey(userId, articleId);
        redisUtil.hset(RedisKeyConstant.MAP_KEY_USER_LIKED, key, LikedStatusEnum.UNLIKE.getCode().toString());
        decrementLikedCount(articleId);
    }

    @Override
    public void incrementLikedCount(Long articleId) {
        redisUtil.hincr(RedisKeyConstant.MAP_KEY_USER_LIKED_COUNT, articleId.toString(), 1);
    }

    @Override
    public void decrementLikedCount(Long articleId) {
        redisUtil.hdecr(RedisKeyConstant.MAP_KEY_USER_LIKED_COUNT, articleId.toString(), 1);
    }

    @Override
    public List<UserLike> getLikedDataFromRedisAndRemove() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyConstant.MAP_KEY_USER_LIKED, ScanOptions.NONE);
        List<UserLike> list = new ArrayList<>();
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> entry = cursor.next();
            String key = (String) entry.getKey();
            String[] split = key.split("::");
            Long userId = Long.parseLong(split[0]);
            Long articleId = Long.parseLong(split[1]);
            Integer value = Integer.parseInt((String) entry.getValue());
            //组装成 UserLike 对象
            UserLike userLike = new UserLike(userId, articleId, value);
            list.add(userLike);

            //存到 list 后从 Redis 中删除
            redisTemplate.opsForHash().delete(RedisKeyConstant.MAP_KEY_USER_LIKED, key);
        }

        return list;
    }

    public List<UserLike> getListByArticleIdsAndUserIdFromRedis(List<Long> articleIds, Long userId) {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyConstant.MAP_KEY_USER_LIKED, ScanOptions.NONE);
        List<UserLike> list = new ArrayList<>();
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> entry = cursor.next();
            String key = (String) entry.getKey();
            String[] split = key.split("::");
            Long redisUserId = Long.parseLong(split[0]);
            if (!redisUserId.equals(userId)){
                continue;
            }
            Long articleId = Long.parseLong(split[1]);
            if (!articleIds.contains(articleId)) {
                continue;
            }
            Integer value = Integer.parseInt((String) entry.getValue());
            //组装成 UserLike 对象
            UserLike userLike = new UserLike(userId, articleId, value);
            list.add(userLike);
        }

        return list;
    }

    @Override
    public List<UserLike> getLikedListByLikedArticleId(Long articleId) {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyConstant.MAP_KEY_USER_LIKED, ScanOptions.NONE);
        List<UserLike> list = new ArrayList<>();
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> entry = cursor.next();
            String key = (String) entry.getKey();
            String[] split = key.split("::");
            Long redisUserId = Long.parseLong(split[0]);

            Long redisArticleId = Long.parseLong(split[1]);
            if (!articleId.equals(redisArticleId)) {
                continue;
            }
            Integer value = Integer.parseInt((String) entry.getValue());
            //组装成 UserLike 对象
            UserLike userLike = new UserLike(redisUserId, redisArticleId, value);
            list.add(userLike);
        }

        return list;
    }


    @Override
    public void transLikedFromRedis2DB() {
        List<UserLike> userLikeList = getLikedDataFromRedisAndRemove();
        log.info("like redis data:{}", JSONUtil.toJsonStr(userLikeList));
        for (UserLike userLike : userLikeList) {
            UserLike old = getOne(new QueryWrapper<UserLike>().eq("user_id", userLike.getUserId()).eq("article_id", userLike.getArticleId()));
            if (old == null) {
                save(userLike);
                continue;
            }
            old.setLikeStatus(userLike.getLikeStatus());
            updateById(old);
        }
    }

    @Override
    public void transLikedNumFromRedis2DB() {
        Map<Long, Long> map = getArticleLikedNumFromRedis(CollUtil.newArrayList(), true);
        if (CollUtil.isEmpty(map.keySet())) {
            log.info("count redis data is empty");
            return;
        }
        log.info("count redis data:{}", JSONUtil.toJsonStr(map));
        List<Article> articles = articleService.listByIds(CollUtil.newArrayList(map.keySet()));
        for (Article article : articles) {
            article.setLikedNum(map.get(article.getId()) + article.getLikedNum());
        }
        articleService.updateBatchById(articles);
    }

    /**
     * 对象列表转换成点赞数
     *
     * @param list
     * @return Map<articleId, LikeCount>
     */
    @Override
    public Map<Long, Long> transferUserLikeList2Count(List<UserLike> list) {
        Map<Long, Long> map = new HashMap<>();
        if (CollUtil.isEmpty(list)) {
            return map;
        }

        Map<Long, List<UserLike>> listMap = list.stream().collect(Collectors.groupingBy(UserLike::getArticleId));
        long count;
        for (Long articleId : listMap.keySet()) {
            count = 0L;
            for (UserLike userLike : listMap.get(articleId)) {
                if (userLike.getLikeStatus().equals(LikedStatusEnum.LIKE.getCode())) {
                    count++;
                }else {
                    count--;
                }
            }
            map.put(articleId, count);
        }

        return map;

    }
}




